<!DOCTYPE html><html lang="[&quot;zh-Hans&quot;,&quot;zh-CN&quot;,&quot;default&quot;]"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"><meta name="description" content="Nextjs+React+KOA-2"><meta name="keywords" content="ssr,nextjs"><meta name="author" content="wenmu"><meta name="copyright" content="wenmu"><title>Nextjs+React+KOA-2 | 温木的博客</title><link rel="shortcut icon" href="/melody-favicon.ico"><link rel="stylesheet" href="/css/index.css?version=1.6.1"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@latest/css/font-awesome.min.css?version=1.6.1"><link rel="dns-prefetch" href="https://cdn.staticfile.org"><link rel="dns-prefetch" href="https://cdn.bootcss.com"><link rel="dns-prefetch" href="https://creativecommons.org"><link rel="dns-prefetch" href="http://ta.qq.com"><script>(function() {
   var hm = document.createElement("script");
   hm.src = "https://tajs.qq.com/stats?sId=&lt;script type=&quot;text/javascript&quot; src=&quot;http://tajs.qq.com/stats?sId=66523541&quot; charset=&quot;UTF-8&quot;&gt;&lt;/script&gt;";
   var s = document.getElementsByTagName("script")[0];
   s.parentNode.insertBefore(hm, s);
 })();</script><script>var GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: undefined,
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  }
} </script><meta name="generator" content="Hexo 4.2.0"></head><body><i class="fa fa-arrow-right" id="toggle-sidebar" aria-hidden="true"></i><div id="sidebar"><div class="toggle-sidebar-info text-center"><span data-toggle="切换文章详情">切换站点概览</span><hr></div><div class="sidebar-toc"><div class="sidebar-toc__title">目录</div><div class="sidebar-toc__progress"><span class="progress-notice">你已经读了</span><span class="progress-num">0</span><span class="progress-percentage">%</span><div class="sidebar-toc__progress-bar"></div></div><div class="sidebar-toc__content"><ol class="toc"><li class="toc-item toc-level-3"><a class="toc-link" href="#Password-方式"><span class="toc-number">1.</span> <span class="toc-text">Password 方式</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Authorize-Code"><span class="toc-number">2.</span> <span class="toc-text">Authorize Code</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Auth-字段"><span class="toc-number">3.</span> <span class="toc-text">Auth 字段</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#请求授权，获取-code"><span class="toc-number">4.</span> <span class="toc-text">请求授权，获取 code</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#请求-token"><span class="toc-number">5.</span> <span class="toc-text">请求 token</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#OAuth-Code-如何保证安全"><span class="toc-number">6.</span> <span class="toc-text">OAuth Code 如何保证安全</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#cookie-和-session"><span class="toc-number">7.</span> <span class="toc-text">cookie 和 session</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#登录后让在当前页"><span class="toc-number">8.</span> <span class="toc-text">登录后让在当前页</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#请求代理"><span class="toc-number">9.</span> <span class="toc-text">请求代理</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#前端和服务端请求的解决"><span class="toc-number">10.</span> <span class="toc-text">前端和服务端请求的解决</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#getInitialProps-的参数为什么和官网的不一样"><span class="toc-number">11.</span> <span class="toc-text">getInitialProps 的参数为什么和官网的不一样</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#getInitialProps返回的对象在组件中获取不到"><span class="toc-number">12.</span> <span class="toc-text">getInitialProps返回的对象在组件中获取不到</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#解决路由切换回来-tab-选中丢失情况"><span class="toc-number">13.</span> <span class="toc-text">解决路由切换回来 tab 选中丢失情况</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#缓存本页的数据"><span class="toc-number">14.</span> <span class="toc-text">缓存本页的数据</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#切换-tab-时，url-的-key-参数没有发生变化"><span class="toc-number">15.</span> <span class="toc-text">切换 tab 时，url 的 key 参数没有发生变化</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#第一次服务端获取到的数据没缓存"><span class="toc-number">16.</span> <span class="toc-text">第一次服务端获取到的数据没缓存</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#使用lru-cache指定缓存策略"><span class="toc-number">17.</span> <span class="toc-text">使用lru-cache指定缓存策略</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#详情页"><span class="toc-number">18.</span> <span class="toc-text">详情页</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#客户端数据缓存"><span class="toc-number">19.</span> <span class="toc-text">客户端数据缓存</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#转换-base64-编码成可识别文字"><span class="toc-number">20.</span> <span class="toc-text">转换 base64 编码成可识别文字</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#转换显示-markdown-文件"><span class="toc-number">21.</span> <span class="toc-text">转换显示 markdown 文件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#markdown-内容中文乱码问题"><span class="toc-number">22.</span> <span class="toc-text">markdown 内容中文乱码问题</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#图片无法显示问题"><span class="toc-number">23.</span> <span class="toc-text">图片无法显示问题</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#生成分析文件"><span class="toc-number">24.</span> <span class="toc-text">生成分析文件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#动态加载不变的文件"><span class="toc-number">25.</span> <span class="toc-text">动态加载不变的文件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#优化-moment-包"><span class="toc-number">26.</span> <span class="toc-text">优化 moment 包</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#去抖动"><span class="toc-number">27.</span> <span class="toc-text">去抖动</span></a></li></ol></div></div><div class="author-info hide"><div class="author-info__avatar text-center"><img src="https://i.loli.net/2020/02/27/xwaBoOj158MfNyq.jpg"></div><div class="author-info__name text-center">wenmu</div><div class="author-info__description text-center">记录一些在工作中或学习新知识时学到的一些知识点；正所谓好记性不如一个烂笔头，温故而知新！</div><hr><div class="author-info-articles"><a class="author-info-articles__archives article-meta" href="/archives"><span class="pull-left">文章</span><span class="pull-right">115</span></a><a class="author-info-articles__tags article-meta" href="/tags"><span class="pull-left">标签</span><span class="pull-right">52</span></a><a class="author-info-articles__categories article-meta" href="/categories"><span class="pull-left">分类</span><span class="pull-right">30</span></a></div></div></div><div id="content-outer"><div class="no-bg" id="top-container"><div id="page-header"><span class="pull-left"> <a id="site-name" href="/">温木的博客</a></span><i class="fa fa-bars toggle-menu pull-right" aria-hidden="true"></i><span class="pull-right menus"><a class="site-page" href="/">首页</a><a class="site-page" href="/archives">文章</a><a class="site-page" href="/tags">标签</a><a class="site-page" href="/categories">分类</a></span></div><div id="post-info"><div id="post-title">Nextjs+React+KOA-2</div><div id="post-meta"><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2020-01-09</time><span class="post-meta__separator">|</span><i class="fa fa-inbox post-meta__icon" aria-hidden="true"></i><a class="post-meta__categories" href="/categories/react/">react</a></div></div></div><div class="layout" id="content-inner"><article id="post"><div class="article-container" id="post-content"><p>OAuth 认证方式有多种，但常用的有两种</p>
<ul>
<li>Password</li>
<li>Authorize Code</li>
</ul>
<h3 id="Password-方式"><a href="#Password-方式" class="headerlink" title="Password 方式"></a>Password 方式</h3><p>这种方式比较简单，就是把用户名和密码传递给认证平台，认证平台返回 token。<br>比如我们使用<code>github</code>，把在<code>github</code>上注册的用户名密码传给<code>github</code>，<code>github</code>认证后返回<code>token</code>。<br>但这有个很明显的弊端：就是当前系统可以知道你在 github 上的账号密码，比较不安全。</p>
<h3 id="Authorize-Code"><a href="#Authorize-Code" class="headerlink" title="Authorize Code"></a>Authorize Code</h3><p>这种方式和<code>Password</code>方式最大的区别就是，这个登录是在认证平台(<code>github</code>)提供的登录页面，也就是在<code>github</code>的网站进行登录和验证，验证通过后返回一个<code>code</code>,然后系统拿着这个<code>code</code>再去认证平台获取<code>token</code>。</p>
<p>这种方式比较安全，当前网站无法接触到你在认证平台上的任何信息。</p>
<h3 id="Auth-字段"><a href="#Auth-字段" class="headerlink" title="Auth 字段"></a>Auth 字段</h3><ul>
<li>client_id</li>
<li>scope</li>
<li>redirect_uri<br>如果传这个参数，则必须和注册的时候输入的地址完全一样，不然获取不到 code；不传则跳转到注册填写的地址。</li>
<li>login</li>
<li>state<br>跳转的时候带上的这个 state 和返回 code 的时候返回的 state 保持一致。</li>
<li>allow_signup<br>是否允许动态注册，就是如果用户没有注册，则自动注册，然后返回信息。</li>
</ul>
<p><a href="https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/" target="_blank" rel="noopener">https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/</a></p>
<h3 id="请求授权，获取-code"><a href="#请求授权，获取-code" class="headerlink" title="请求授权，获取 code"></a>请求授权，获取 code</h3><p>GET <a href="https://github.com/login/oauth/authorize" target="_blank" rel="noopener">https://github.com/login/oauth/authorize</a><br>链接上可以传上面的 auth 字段参数</p>
<h3 id="请求-token"><a href="#请求-token" class="headerlink" title="请求 token"></a>请求 token</h3><p>请求 token 时，client_id、client_secret、code 是必须的，<br>redirect_uri、state 不是必须的</p>
<p>POST <a href="https://github.com/login/oauth/access_token" target="_blank" rel="noopener">https://github.com/login/oauth/access_token</a></p>
<h3 id="OAuth-Code-如何保证安全"><a href="#OAuth-Code-如何保证安全" class="headerlink" title="OAuth Code 如何保证安全"></a>OAuth Code 如何保证安全</h3><ul>
<li>一次性的 code<br>code 获取了 token 后就失效了</li>
<li>id+secret<br>没这两个值，是无法获取 token 的</li>
<li>redirect_uri<br>如果这个和注册时不一致，就会报错</li>
</ul>
<h3 id="cookie-和-session"><a href="#cookie-和-session" class="headerlink" title="cookie 和 session"></a>cookie 和 session</h3><p>cookie 是存储在客户端，每次请求都会携带它，不管是文件请求还是 api 的请求，服务端可以读取到 cookie 的值。</p>
<h3 id="登录后让在当前页"><a href="#登录后让在当前页" class="headerlink" title="登录后让在当前页"></a>登录后让在当前页</h3><p>这个实现起来也很简单，就是登录之前先把当前页面的地址存起来，登录成功后再跳转到即可。<br>通过<code>next/router</code>中的<code>withRouter</code>可以获取到当前页面的<code>url</code>地址<br>实现思路有两种：</p>
<ul>
<li>在登录事件中，先请求服务器，把 url 地址存起来，然后再跳转到登录页面</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> onGoToAuth = useCallback(<span class="function"><span class="params">e</span> =&gt;</span> &#123;</span><br><span class="line">  e.preventDefault();</span><br><span class="line">  axios.get(<span class="string">`prepare-auth?url=<span class="subst">$&#123;router.asPath&#125;</span>`</span>).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (res.status === <span class="number">200</span>) &#123;</span><br><span class="line">      location.href = publicRuntimeConfig.OAUTH_URL;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">"prepare-auth failed"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;, []);</span><br><span class="line"></span><br><span class="line">server.use(<span class="keyword">async</span> (ctx, next) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; path, method &#125; = ctx;</span><br><span class="line">  <span class="keyword">if</span> (path === <span class="string">"/prepare-auth"</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; query &#125; = ctx;</span><br><span class="line">    ctx.session.urlBeforeOAuth = query.url;</span><br><span class="line">    ctx.body = <span class="string">"ready"</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">await</span> next();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<ul>
<li>另一种是超链接直接链接过去，在服务端跳转</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">&lt;a href=&#123;<span class="string">`prepare-auth?url=<span class="subst">$&#123;router.asPath&#125;</span>`</span>&#125;&gt;</span><br><span class="line">  &lt;Avatar sieze=&#123;<span class="number">40</span>&#125; icon=<span class="string">"user"</span> /&gt;</span><br><span class="line">&lt;<span class="regexp">/a&gt;;</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">server.use(async (ctx, next) =&gt; &#123;</span></span><br><span class="line"><span class="regexp">  const &#123; path, method &#125; = ctx;</span></span><br><span class="line"><span class="regexp">  if (path === "/</span>prepare-auth<span class="string">") &#123;</span></span><br><span class="line"><span class="string">    const &#123; query &#125; = ctx;</span></span><br><span class="line"><span class="string">    ctx.session.urlBeforeOAuth = query.url;</span></span><br><span class="line"><span class="string">    //ctx.body = "</span>ready<span class="string">";</span></span><br><span class="line"><span class="string">    ctx.redirect(config.github.OAUTH_URL);</span></span><br><span class="line"><span class="string">  &#125; else &#123;</span></span><br><span class="line"><span class="string">    await next();</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">&#125;);</span></span><br></pre></td></tr></table></figure>

<h3 id="请求代理"><a href="#请求代理" class="headerlink" title="请求代理"></a>请求代理</h3><p>上一个项目使用的是 express 的 http 代理插件，轻松搞定，这个是纯手写。<br>其实就是根据规则转发请求。</p>
<h3 id="前端和服务端请求的解决"><a href="#前端和服务端请求的解决" class="headerlink" title="前端和服务端请求的解决"></a>前端和服务端请求的解决</h3><p><code>getInitalProps</code>在客户端和服务端都会执行，当在客户端发起请求时比价好办，上面的代理直接转给了真正的服务服务器。但在服务端执行时，由于写的是相对路径，服务器默认会在自己身上找，那这个请求肯定是找不到的，所以请求是服务端发出的还是客户端发出的，需要区别对待。</p>
<blockquote>
<p>上一个项目是通过<code>createStore</code>的第三个参数<code>thunk.withExtraArgument</code>来解决。可参考</p>
</blockquote>
<p>这里做的比较直接，直接是在发出请求的时候判断下，是服务端还是客户端，然后做响应的处理，服务端的话，就要以全路径的方式请求。把路径拼全就行了。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> isServer = <span class="keyword">typeof</span> <span class="built_in">window</span> === <span class="string">"undefined"</span>;</span><br><span class="line"><span class="keyword">const</span> github_base_url = <span class="string">"https://api.github.com"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">requestGithub</span>(<span class="params">method, url, data, headers</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">await</span> axios(&#123;</span><br><span class="line">    method,</span><br><span class="line">    url: <span class="string">`<span class="subst">$&#123;github_base_url&#125;</span><span class="subst">$&#123;url&#125;</span>`</span>,</span><br><span class="line">    data,</span><br><span class="line">    headers,</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">request</span>(<span class="params">&#123; method = <span class="string">"GET"</span>, url, data = &#123;&#125; &#125;, req, res</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!url) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="built_in">Error</span>(<span class="string">"url muste provide."</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (isServer) &#123;</span><br><span class="line">    <span class="keyword">const</span> session = req.session;</span><br><span class="line">    <span class="keyword">const</span> githubAuth = session.githubAuth || &#123;&#125;;</span><br><span class="line">    <span class="keyword">const</span> headers = &#123;&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (githubAuth.access_token) &#123;</span><br><span class="line">      headers[<span class="string">"Authorization"</span>] = <span class="string">`<span class="subst">$&#123;githubAuth.token_type&#125;</span> <span class="subst">$&#123;githubAuth.access_token&#125;</span>`</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">await</span> requestGithub(method, url, data, headers);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">await</span> axios(&#123;</span><br><span class="line">      method,</span><br><span class="line">      url: <span class="string">`/github<span class="subst">$&#123;url&#125;</span>`</span>,</span><br><span class="line">      data,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>###获取 post 请求的数据<br>使用<code>koa-body</code>插件即可，安装完后在 server 中注册即可</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">const server &#x3D; new Koa();</span><br><span class="line">&#x2F;&#x2F;这样就可以通过ctx.request.body来获取post的值</span><br><span class="line">server.use(koaBody());</span><br></pre></td></tr></table></figure>

<p>在 post 的请求里面，就可以通过<code>ctx.request.body</code>的方式来获取 post 传递的值。</p>
<h3 id="getInitialProps-的参数为什么和官网的不一样"><a href="#getInitialProps-的参数为什么和官网的不一样" class="headerlink" title="getInitialProps 的参数为什么和官网的不一样"></a>getInitialProps 的参数为什么和官网的不一样</h3><p>今天在做项目时，教程里<code>getInitialProps</code>的参数有<code>ctx</code>参数，而页面的<code>getInitialProps</code>中没有。结果看了下官网给的绑定 redux 的 demo，在绑定 redux 的地方也是有 ctx 的。因为我自定义了_app.js 文件，所以怀疑可能是这引起的。<br>因为所有的页面都会执行 app.js，所以这的定义可能会影响到组件中的功能。<br>看了下我的<code>_app.js</code>中是没有定义<code>getInitialProps</code>，所以各个组件的<code>getInitialProps</code>是默认的，官网上显示<code>getInitialProps</code>默认是没有 ctx 属性的，所以就在<code>_app.js</code>中自定义了<code>getInitialProps</code>，并验证这里是有<code>ctx</code>属性的，所以透传下去，各个组件也就也有<code>ctx</code>属性了。</p>
<h3 id="getInitialProps返回的对象在组件中获取不到"><a href="#getInitialProps返回的对象在组件中获取不到" class="headerlink" title="getInitialProps返回的对象在组件中获取不到"></a><code>getInitialProps</code>返回的对象在组件中获取不到</h3><p>原因是在<code>_app.js</code>中我自定义了<code>getInitialProps</code>，但是没有把从<code>getInitialProps</code>获取到的值返回方式和接收方式不一致</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">  <span class="keyword">static</span> <span class="keyword">async</span> getInitialProps(appContext) &#123;</span><br><span class="line">    <span class="keyword">let</span> pageProps = &#123;&#125;;</span><br><span class="line">    <span class="keyword">if</span> (appContext.Component.getInitialProps) &#123;</span><br><span class="line">      pageProps = <span class="keyword">await</span> appContext.Component.getInitialProps(appContext);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// return pageProps;   错误写法</span></span><br><span class="line">   <span class="keyword">return</span> &#123; <span class="attr">pageProps</span>: &#123; ...pageProps &#125; &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 接收是这样的，要保持一致</span></span><br><span class="line">&lt;Component &#123;...pageProps&#125; /&gt;</span><br></pre></td></tr></table></figure>

<h3 id="解决路由切换回来-tab-选中丢失情况"><a href="#解决路由切换回来-tab-选中丢失情况" class="headerlink" title="解决路由切换回来 tab 选中丢失情况"></a>解决路由切换回来 tab 选中丢失情况</h3><p>使用<code>next/router</code>提供的 router 功能，把 tab key 的值放到 url 中，然后从 url 中获取 tab key 的值即可。<br>这样切换路由再回来时，url 中是带参数的。</p>
<h3 id="缓存本页的数据"><a href="#缓存本页的数据" class="headerlink" title="缓存本页的数据"></a>缓存本页的数据</h3><p>由于页面上数据变化不大，因此可以在本页缓存数据，以避免每次点击 tab 都会重新请求数据<br>在页面中声明一个全局变量，存储远程获取的数据，然后在<code>getInitialProps</code>中判断，如果缓存变量中有数据就直接返回不再请求。</p>
<p>// index.js</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Index</span>(<span class="params"></span>) </span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> cachedUserRepos;</span><br><span class="line">Index.getInitialProps = <span class="keyword">async</span> (&#123; ctx &#125;) =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (cachedUserRepos) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      userRepos: cachedUserRepos,</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> userRepos = <span class="keyword">await</span> api.request(&#123; <span class="attr">url</span>: <span class="string">"/user/repos"</span> &#125;, ctx.req, ctx.res);</span><br><span class="line">  cachedUserRepos = userRepos.data;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    userRepos: cachedUserRepos,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>但这有一个弊端，如果是首页，也就是当<code>getInitialProps</code>是在服务端执行时，全局变量的值会一直存在，即使在新的客户端用新的用户登录，全局变量中的数据仍然存在；这是为什么呢？<br>这是<code>因为全局变量是这个模块的全局变量，并不是Index组件(或方法)的变量，即使重新渲染，原来缓存的数据仍然存在；当nextjs启动服务加载了index.js模块后，这个全局变量就一直存在。</code>我们赋值过后它就一直有值。所以需要判断如果是服务端渲染就不从这个变量中取值，也不给他们赋值；</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> isServer = <span class="keyword">typeof</span> <span class="built_in">window</span> === <span class="string">"undefined"</span>;</span><br><span class="line">Index.getInitialProps = <span class="keyword">async</span> (&#123; ctx &#125;) =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (!isServer) &#123;</span><br><span class="line">    <span class="keyword">if</span> (cachedUserRepos &amp;&amp; cachedUserStaredRepos) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        userRepos: cachedUserRepos,</span><br><span class="line">        userStaredRepos: cachedUserStaredRepos,</span><br><span class="line">      &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> userRepos = <span class="keyword">await</span> api.request(&#123; <span class="attr">url</span>: <span class="string">"/user/repos"</span> &#125;, ctx.req, ctx.res);</span><br><span class="line">  <span class="keyword">const</span> userStaredRepos = <span class="keyword">await</span> api.request(&#123; <span class="attr">url</span>: <span class="string">"/user/starred"</span> &#125;, ctx.req, ctx.res);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (!isServer) &#123;</span><br><span class="line">    cachedUserRepos = userRepos.data;</span><br><span class="line">    cachedUserStaredRepos = userStaredRepos.data;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    userRepos: userRepos.data,</span><br><span class="line">    userStaredRepos: userStaredRepos.data,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>模块中的其他全局变量也要注意这个问题的存在！！！</p>
</blockquote>
<h3 id="切换-tab-时，url-的-key-参数没有发生变化"><a href="#切换-tab-时，url-的-key-参数没有发生变化" class="headerlink" title="切换 tab 时，url 的 key 参数没有发生变化"></a>切换 tab 时，url 的 key 参数没有发生变化</h3><p>这需要吧 withRouter 放到 connect 外面，放里面就有上面的说的问题</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> withRouter(connect(<span class="function"><span class="params">state</span> =&gt;</span> (&#123; <span class="attr">user</span>: state.user &#125;))(Index));</span><br></pre></td></tr></table></figure>

<h3 id="第一次服务端获取到的数据没缓存"><a href="#第一次服务端获取到的数据没缓存" class="headerlink" title="第一次服务端获取到的数据没缓存"></a>第一次服务端获取到的数据没缓存</h3><p>第一次服务端获取到的数据没缓存，当第一次切换 tab 时仍重新请求了数据，这可以使用<code>useEffect</code>来解决，第一次页面加载时，如果 props 中有值就缓存起来</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">useEffect(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (userRepos &amp;&amp; userStaredRepos) &#123;</span><br><span class="line">    cachedUserRepos = userRepos;</span><br><span class="line">    cachedUserStaredRepos = userStaredRepos;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;, []);</span><br></pre></td></tr></table></figure>

<h3 id="使用lru-cache指定缓存策略"><a href="#使用lru-cache指定缓存策略" class="headerlink" title="使用lru-cache指定缓存策略"></a>使用<code>lru-cache</code>指定缓存策略</h3><p>上面的缓存没问题，但是缓存是一直存在的，不会过期，这不是太好，可以通过<code>lru-cache</code>来制定有时效的换成策略。<br>这个使用起来比较简单，查看官网写即可。</p>
<h3 id="详情页"><a href="#详情页" class="headerlink" title="详情页"></a>详情页</h3><p>查询、翻页等操作都反应到 url 参数中，这样页面进行回退等操作时，能记录上次的状态。</p>
<blockquote>
<p>github 限制了列表请求最多只返回 1000 条数据的请求，超过第 1000 条数据的请求就报错不返回了，比如你每页 20 条，最大只能访问 50 页；因为没人会翻在 000 条数据内还找不到要找的库。</p>
</blockquote>
<h3 id="客户端数据缓存"><a href="#客户端数据缓存" class="headerlink" title="客户端数据缓存"></a>客户端数据缓存</h3><p>有很多数据是变化不大的，可以针对客户端请求做缓存策略。注意服务端渲染时不要做缓存。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> isServer = <span class="keyword">typeof</span> <span class="built_in">window</span> === <span class="string">"undefined"</span>;</span><br></pre></td></tr></table></figure>

<h3 id="转换-base64-编码成可识别文字"><a href="#转换-base64-编码成可识别文字" class="headerlink" title="转换 base64 编码成可识别文字"></a>转换 base64 编码成可识别文字</h3><p>window 全局提供了一个方法可以转换，方法就是<code>atob(content)</code>，但是服务端没这个方法，所以可以安装<code>atob</code>包，在服务端把这个包赋给全局变量</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> atob = <span class="built_in">require</span>(<span class="string">"atob"</span>);</span><br><span class="line">global.atob = atob;</span><br></pre></td></tr></table></figure>

<h3 id="转换显示-markdown-文件"><a href="#转换显示-markdown-文件" class="headerlink" title="转换显示 markdown 文件"></a>转换显示 markdown 文件</h3><p><code>markdown</code>文件默认读取出的内容是 base64 字节流，通过<code>atob</code>方法可以转换成它本来的内容，但是特殊符号例如空格仍是<code>&amp;nbsp;</code>,所以仍需转换真正的 html 字符串，通过<code>markdown-it</code>组件可以完成<br>react 做了 xss 攻击处理，所以需要通过下面的方式显示 html 字符串</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 把base64转换成了原来的字符串，但是特殊符号还不是html的显示方式</span></span><br><span class="line"><span class="keyword">const</span> content = atob(readme.content);</span><br><span class="line"><span class="comment">// 转换成html的方式显示</span></span><br><span class="line"><span class="keyword">const</span> html = md.render(content);</span><br><span class="line"><span class="comment">// 由于react做了防XSS(跨站脚本攻击)的处理，所以不能直接把html字符串进行显示</span></span><br><span class="line"><span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">dangerouslySetInnerHTML</span>=<span class="string">&#123;&#123;</span> <span class="attr">__html:</span> <span class="attr">html</span> &#125;&#125;&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br></pre></td></tr></table></figure>

<h3 id="markdown-内容中文乱码问题"><a href="#markdown-内容中文乱码问题" class="headerlink" title="markdown 内容中文乱码问题"></a>markdown 内容中文乱码问题</h3><p>需要把 base64 转换 utf8</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">base64_to_utf8</span>(<span class="params">str</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">decodeURIComponent</span>(<span class="built_in">escape</span>(atob(str)));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> content = base64_to_utf8(readme.content);</span><br><span class="line"><span class="keyword">const</span> html = md.render(content);</span><br></pre></td></tr></table></figure>

<h3 id="图片无法显示问题"><a href="#图片无法显示问题" class="headerlink" title="图片无法显示问题"></a>图片无法显示问题</h3><p>markdown 中的 html 是无法直接显示，通过制定显示 markdown 中的 html 可以解决</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> md = <span class="keyword">new</span> MarkdownIt(&#123;</span><br><span class="line">  html: <span class="literal">true</span> <span class="comment">/* 显示markdown中的html文件 */</span>,</span><br><span class="line">  linkify: <span class="literal">true</span> <span class="comment">/* 文档中的链接可以点击*/</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>使用<code>github-markdown-css</code>样式，使 markdown 内容显示更完美</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npm i github-markdown-css</span><br><span class="line"></span><br><span class="line">import <span class="string">'github-markdown-css'</span></span><br></pre></td></tr></table></figure>

<h3 id="生成分析文件"><a href="#生成分析文件" class="headerlink" title="生成分析文件"></a>生成分析文件</h3><p>首先 next 对应的插件<code>@zeit/next-bundle-analyzer</code>，然后在<code>next.config.js</code>中进行配置，并设置打包命令</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// next.config.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = withBundleAnalyzer(</span><br><span class="line">  withCss(</span><br><span class="line">    <span class="comment">// 下面的配置就是使用webpack的分析工具，生成分析文件</span></span><br><span class="line">    analyzeBrowser: [<span class="string">"browser"</span>, <span class="string">"both"</span>].includes(process.env.BUNDLE_ANALYZE),</span><br><span class="line">    bundleAnalyzerConfig: &#123;</span><br><span class="line">      server: &#123;</span><br><span class="line">        analyzerMode: <span class="string">"static"</span>,</span><br><span class="line">        <span class="comment">// 服务端分析文件生成路径</span></span><br><span class="line">        reportFilename: <span class="string">"../bundles/server.html"</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      browser: &#123;</span><br><span class="line">        analyzerMode: <span class="string">"static"</span>,</span><br><span class="line">        reportFilename: <span class="string">"../bundles/client.html"</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">// package.json</span></span><br><span class="line"> <span class="string">"analyze:browser"</span>: <span class="string">"cross-env BUNDLE_ANALYZE=browser next build"</span></span><br></pre></td></tr></table></figure>

<h3 id="动态加载不变的文件"><a href="#动态加载不变的文件" class="headerlink" title="动态加载不变的文件"></a>动态加载不变的文件</h3><p>封装了一个 Markdown 文件显示的组件，这个组件的内容是不变的，所以可以动态加载，动态加载的文件的会被 webpack 单独打包，因为它的打包后的 hash 不变，可以方便浏览器缓存，提高体验。</p>
<h3 id="优化-moment-包"><a href="#优化-moment-包" class="headerlink" title="优化 moment 包"></a>优化 moment 包</h3><p><code>moment</code>中包含很多我们用不上的文件，比如它的国际化文件中包含了很多国家的语言文件，我们只需要中文的，所以只需要加载中文的语言包就行。</p>
<ul>
<li>配置<code>webpack</code>忽略<code>moment</code>组件的所有语言包</li>
<li>在使用<code>moment</code>的地方手动引入要用的语言包</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// next.config.js</span></span><br><span class="line">  withCss(&#123;</span><br><span class="line">    webpack(config) &#123;</span><br><span class="line">      config.plugins.push(<span class="keyword">new</span> webpack.IgnorePlugin(<span class="regexp">/^\.\/locale$/</span>, /moment$/));</span><br><span class="line">      <span class="keyword">return</span> config;</span><br><span class="line">    &#125;,</span><br><span class="line">....</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用的地方</span></span><br><span class="line"><span class="keyword">import</span> moment <span class="keyword">from</span> <span class="string">"moment"</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">"moment/locale/zh-cn"</span>;</span><br></pre></td></tr></table></figure>

<h3 id="去抖动"><a href="#去抖动" class="headerlink" title="去抖动"></a>去抖动</h3><p>做自动匹配查询时，可以使用<code>lodash/debounce</code>来去抖动</p>
<p>Gatsby</p>
</div></article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="mailto:undefined">wenmu</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="http://blog.wangpengpeng.site/2020/01/09/Nextjs+React+KOA-2/">http://blog.wangpengpeng.site/2020/01/09/Nextjs+React+KOA-2/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank" rel="noopener">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="http://blog.wangpengpeng.site">温木的博客</a>！</span></div></div><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/ssr/">ssr</a><a class="post-meta__tags" href="/tags/nextjs/">nextjs</a></div><div class="post-qr-code"><div class="post-qr-code-item"><img class="post-qr-code__img" src="https://i.loli.net/2020/02/27/hOBzIxFDJMCrmtN.jpg"><div class="post-qr-code__desc">微信打赏</div></div></div><nav id="pagination"><div class="prev-post pull-left"><a href="/2020/01/09/Nextjs+React+KOA-1/"><i class="fa fa-chevron-left">  </i><span>Nextjs+React+KOA-1</span></a></div><div class="next-post pull-right"><a href="/2020/01/09/React-%E6%96%B0%E7%89%B9%E6%80%A7Hooks/"><span>React-新特性Hooks</span><i class="fa fa-chevron-right"></i></a></div></nav><div id="vcomment"></div><script src="https://cdn1.lncld.net/static/js/3.0.4/av-min.js"></script><script src="https://cdn.jsdelivr.net/npm/valine/dist/Valine.min.js"></script><script>var notify = 'false' == true ? true : false;
var verify = 'false' == true ? true : false;
var GUEST_INFO = ['nick','mail','link'];
var guest_info = 'nick,mail,link'.split(',').filter(function(item){
  return GUEST_INFO.indexOf(item) > -1
});
guest_info = guest_info.length == 0 ? GUEST_INFO :guest_info;
window.valine = new Valine({
  el:'#vcomment',
  notify:notify,
  verify:verify,
  appId:'U1QqpeQNuBnRsfygHk1GQ30q-gzGzoHsz',
  appKey:'dunDQzEseDzgsKgw4qLQcog1',
  placeholder:'支持markdown！',
  avatar:'wavatar',
  guest_info:guest_info,
  pageSize:'20',
  lang: 'zh-cn'
})</script></div></div><footer><div class="layout" id="footer"><div class="copyright">&copy;2019 - 2020 By wenmu</div><div class="framework-info"><span>驱动 - </span><a href="http://hexo.io" target="_blank" rel="noopener"><span>Hexo</span></a><span class="footer-separator">|</span><span>主题 - </span><a href="https://github.com/Molunerfinn/hexo-theme-melody" target="_blank" rel="noopener"><span>Melody</span></a></div><div class="busuanzi"><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><span id="busuanzi_container_page_pv">该文访问量<span id="busuanzi_value_page_pv"></span><span></span></span></div></div></footer><i class="fa fa-arrow-up" id="go-up" aria-hidden="true"></i><script src="https://cdn.jsdelivr.net/npm/animejs@latest/anime.min.js"></script><script src="https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-animate@latest/velocity.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-ui-pack@latest/velocity.ui.min.js"></script><script src="/js/utils.js?version=1.6.1"></script><script src="/js/fancybox.js?version=1.6.1"></script><script src="/js/sidebar.js?version=1.6.1"></script><script src="/js/copy.js?version=1.6.1"></script><script src="/js/fireworks.js?version=1.6.1"></script><script src="/js/transition.js?version=1.6.1"></script><script src="/js/scroll.js?version=1.6.1"></script><script src="/js/head.js?version=1.6.1"></script><script>if(/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent)) {
  $('#nav').addClass('is-mobile')
  $('footer').addClass('is-mobile')
}</script></body></html>